package compile

import (
	"fmt"
	"os"
	"path/filepath"
	"sail/classpath"
	"strings"
)

func cleanUpImports(importDeclares []*ImportDeclare) []*ImportDeclare {
	var stringParts [][]string
	var newImpDecls []*ImportDeclare

	for _, decls := range importDeclares {
		if sq := dotPathToStringSlice(decls.ImportParts); !stringSliceIn(sq, stringParts) {
			newImpDecls = append(newImpDecls, decls)
			stringParts = append(stringParts, sq)
		}
	}

	return newImpDecls
}

func separateImportDeclares(importDeclare []*ImportDeclare) (
	normalImportDecls []*ImportDeclare,
	staticImpDecls []*ImportDeclare, wildcardImpDecls []*ImportDeclare) {

	for _, decl := range importDeclare {
		if decl.IsStatic {
			staticImpDecls = append(staticImpDecls, decl)
		} else if decl.WithWildcard {
			wildcardImpDecls = append(wildcardImpDecls, decl)
		} else {
			normalImportDecls = append(normalImportDecls, decl)
		}
	}
	return
}

// make single package entry for normal import declares with SAME BEGIN PART.
//! importDeclares IS A SLICE WITH NORMAL IMPORT DECLARES.
// 'import sail.basic.ArrayList'
// 'import sail.site.Movie'
// 'import sail.site.Show'
// A and B has SAME BEGIN PART ('sail').
// So the result is:
// sail ___ basic __ ArrayList
//       |_ site ___ Movie
//                |_ Show
func makeSingleNormalPackageEntry(
	importDeclares []*ImportDeclare,
	targetPartName string, targetIndex int) *PackageEntry {

	packageEntry := new(PackageEntry)
	packageEntry.EntryName = targetPartName

	var childEntries []*PackageEntry
	var doneNames []string

	// pick good import declares.
	for _, decl := range importDeclares {
		strParts := dotPathToStringSlice(decl.ImportParts)

		if strParts[targetIndex] == targetPartName {
			if targetIndex+2 < len(strParts) {
				nextTargetName := strParts[targetIndex+1]

				if !stringIn(strParts[targetIndex+1], doneNames) {
					childEntries = append(childEntries, makeSingleNormalPackageEntry(
						importDeclares, nextTargetName, targetIndex+1))

					doneNames = append(doneNames, strParts[targetIndex+1])
				}

			} else if targetIndex+1 < len(strParts) {
				packageEntry.Classes = append(packageEntry.Classes, &ClassEntry{
					ClassName: strParts[targetIndex+1],
					Path:      strParts,
				})
			}
		}
	}

	packageEntry.Children = childEntries

	return packageEntry
}

// make package entries for NORMAL import declares.
func makeNormalPackageEntries(importDeclares []*ImportDeclare) []*PackageEntry {
	var doneBegins []string
	var pkgEntries []*PackageEntry

	for _, decl := range importDeclares {
		begin := dotPathToStringSlice(decl.ImportParts)[0]

		if !stringIn(begin, doneBegins) {
			pkgEntries = append(
				pkgEntries, makeSingleNormalPackageEntry(importDeclares, begin, 0))
			doneBegins = append(doneBegins, begin)
		}
	}

	return pkgEntries
}

// :param files  the result of expending wildcard.
//
// expand 'entry'.Classes (with wildcard) with 'files'
// example:
//
// import sail.site.*;
// (sail/site/ have ['Nezha.sail', 'Aobing.sail'])
//
// the Classes member of package entry 'site' only have
// ClassEntry {
// 	  ClassName: "*",
//    Path:		 ["sail", "site", "*"]
// }
// this function will replace ClassEntry '*' with list of ClassMember in directory.
func replaceClassWithFiles(files []string, entry *PackageEntry) {
	var classes []*ClassEntry

	for _, fileName := range files {
		fileExt := filepath.Ext(fileName)
		fileBase := filepath.Base(fileName)
		className := strings.TrimSuffix(fileBase, fileExt)

		oldClassPath := entry.Classes[0].Path
		if len(oldClassPath) > 1 {
			oldClassPath = oldClassPath[:len(oldClassPath)-1]
		}

		classes = append(classes, &ClassEntry{
			ClassName: className,
			Path:      append(oldClassPath, className),
		})
	}

	entry.Classes = classes // replace
}

func makeWildcardPackageEntries(importDeclares []*ImportDeclare,
	opt *Option) []*PackageEntry {

	var doneBegins []string
	var wildcardPkgEntries []*PackageEntry

	// add '*' to the end of path
	for _, decl := range importDeclares {
		decl.ImportParts.Parts = append(decl.ImportParts.Parts, &Identifier{
			Value: "*",
		})
	}

	for _, decl := range importDeclares {
		begin := dotPathToStringSlice(decl.ImportParts)[0]

		if !stringIn(begin, doneBegins) {
			pkg := makeSingleNormalPackageEntry(importDeclares, begin, 0)

			err := pkg.Walk(func(entry *PackageEntry) error {
				if length := len(entry.Classes); length != 1 {
					return nil
				}

				if class := entry.Classes[0]; class.ClassName == "*" {
					dirPath := strings.Join(
						class.Path[:len(class.Path)-1], string(filepath.Separator))

					dirNames, err := opt.Classpath.ListDir(dirPath)
					if err == classpath.IsFile || os.IsNotExist(err) {
						fmt.Printf("Invalid import path : '%s' (%s)\n",
							strings.Join(class.Path, "."), dirPath)
					} else if err != nil {
						unexpectedError(err, "")
					} else {
						replaceClassWithFiles(dirNames, entry)
					}
				}

				return nil
			})

			if err != nil {
				unexpectedError(err, "")
			}

			wildcardPkgEntries = append(wildcardPkgEntries, pkg)
			doneBegins = append(doneBegins, begin)
		}
	}

	return wildcardPkgEntries
}

func makePackageEntries(importDeclares []*ImportDeclare, opt *Option) (
	norEntries, wildcardEntries, staticEntries []*PackageEntry) {

	norImp, staticImp, wildcardImp := separateImportDeclares(importDeclares)

	norEntries = makeNormalPackageEntries(norImp)
	var usefulEntries []*PackageEntry

	for _, entry := range norEntries {
		useful := entry.selfCheckAndClearUp(opt.Classpath, opt.ErrorReporter)

		if useful {
			usefulEntries = append(usefulEntries, entry)
		}
	}
	norEntries = usefulEntries

	wildcardEntries = makeWildcardPackageEntries(wildcardImp, opt)

	_ = staticImp // TODO Finish it!

	return
}

// Class full name -> Entries
// Class -> Symbols
// ImportDecl -> Imports
func ScopeImports(importDeclares []*ImportDeclare, scope *Scope, opt *Option) {
	importDeclares = cleanUpImports(importDeclares)

	for _, impDecl := range importDeclares {
		scope.Imports = append(scope.Imports, impDecl)
	}

	norEntries, wildcardEntries, staticEntries := makePackageEntries(importDeclares, opt)

	_, _, _ = norEntries, wildcardEntries, staticEntries
}
